3.7.ACL权限管理(zero-rbac)
白皮书中的权限管理主要包括两部分:管理端和消费端:
-
管理端:可扩展面板式架构,现阶段版本支持 菜单管理、流程定制 两类,您可以扩展自己所需的不同权限管理的结构。
-
消费端:XBAC模型:基于角色的RBAC(Role)、基于属性的ABAC(Attribute)、基于策略的PBAC(Policy)。
|
Zero在线教程中讲解过可插拔模式的认证组件,此处略过认证部分( |
3.7.1.消费端
3.7.1.1.XBAC模型
Zero中的基本权限主体模型如下:
上图中的核心概念如下:
| 实体表 | 概念 | 含义 |
|---|---|---|
S_USER |
用户 |
登录系统专用账号信息表。 |
S_ROLE |
角色 |
和账号关联的角色信息表。 |
S_GROUP |
用户组 |
和账号关联的用户组信息表。 |
S_PERMISSION |
权限 |
和角色相关联的权限记录表。 |
S_PERM_SET |
权限集 |
服务于权限定义的权限集合表,主要用于管理段构造权限集合实现批量授权。 |
S_ACTION |
操作 |
隶属于权限记录的操作集合,操作最终会绑定到对应的操作主体上。 |
常用的基本配置中,只要资源所需操作级别和基础算法模型计算的最终权限路径和用户所拥有的权限路径是通的,那么就可以实现基础权限的认证,这部分内容在完成本章节所有讲解之后会有更深入的说明。 |
安全操作 S_ACTION 对端关联到资源表 S_RESOURCE 和资源形成1对1的绑定,资源中定义的就是访问资源的基本要求,而S_ACTION中计算出来的结果就是登录账号所拥有的资源访问资格,当资源访问资格满足了资源基本要求时就认证通过,证明当前账号有权限访问该资源。但是、但是、但是——此处只是解决了账号:能不能 的问题,在资源访问之后还会有两张专用的数据表来执行 访问多少 的问题,这是Zero中数据域的实现原理,在后续的 N维安全视图 中加以说明。
3.7.1.2.多态身份(Profile)
Zero中由于复杂的多对多结构,最终会形成不同的多态身份(Profile),S_RESOURCE 表中有如下字段对资源访问资格执行定义:
| 字段名 | 含义 | 取值 |
|---|---|---|
MODE_ROLE |
按角色查找资源的模式 |
UNION、EAGER、LAZY、INTERSECT |
MODE_GROUP |
按用户组查找资源的模式 |
HORIZON、CRITICAL、OVERLOOK |
MODE_TREE |
用户组继承和非继承树模式查找 |
EXTEND、PARENT、CHILD、INHERIT |
上述取值是资源对多态身份的定义,最终形成的多态身份 Profile 的值列表如下(举例定义角色和组的优先级):
|
不含组模式
假设用户包含如下信息,这种模式(高频使用模式)下 MODE_GROUP / MODE_TREE 都设置成 NULL:
R1(H):P1、P2、P3,高优先级角色,包含三条权限记录。
R2(L):P2、P4,低优先级角色,包含两条权限记录。
| 值 | 含义 |
|---|---|
USER_UNION |
|
USER_EAGER |
|
USER_LAZY |
|
USER_INTERSECT |
|
用户组模式
假设用户包含如下信息:
用户组结构如下(此处不列举组所对应的权限集):
G10
/ \
G20 G21
/ \ \
G30 G31 G32
而登录用户只包含如下三个用户组:
G20(H):高优先级用户组
G31(M):中优先级用户组
G32(L):低优先级用户组
全量优先级:
G10 > G20 > G21 > G30 > G31 > G32
| 值 | 组计算 | 角色计算 | 计算流程 |
|---|---|---|---|
HORIZON_UNION |
UNION模式计算单个用户组关联角色权限集,再将三个组的权限集合并。 |
||
HORIZON_EAGER |
EAGER模式计算单个用户组关联角色权限集,再将三个组的权限集合并。 |
||
HORIZON_LAZY |
LAZY模式计算单个用户组关联角色权限集,再将三个组的权限集合并。 |
||
HORIZON_INTERSECT |
INTERSECT模式计算单个用户组关联角色权限集,再将三个组的权限集合并。 |
||
CRITICAL_UNION |
UNION模式计算高优先级组的权限集。 |
||
CRITICAL_EAGER |
EAGER模式计算高优先级组的权限集。 |
||
CRITICAL_LAZY |
LAZY模式计算高优先级组的权限集。 |
||
CRITICAL_INTERSECT |
INTERSECT模式计算高优先级的权限集。 |
||
OVERLOOK_UNION |
UNION模式计算低优先级组的权限集。 |
||
OVERLOOK_EAGER |
EAGER模式计算低优先级组的权限集。 |
||
OVERLOOK_LAZY |
LAZY模式计算低优先级组的权限集。 |
||
OVERLOOK_INTERSECT |
INTERSECT模式计算低优先级组的权限集。 |
||
PARENT_HORIZON_UNION |
先查找三个组的父组,再按UNION模式计算权限集。 |
||
PARENT_HORIZON_EAGER |
先查找三个组的父组,再按EAGER模式计算权限集。 |
||
PARENT_HORIZON_LAZY |
先查找三个组的父组,再按LAZY模式计算权限集。 |
||
PARENT_HORIZON_INTERSECT |
先查找三个组的父组,再按INTERSECT模式计算权限集。 |
||
PARENT_CRITICAL_UNION |
查找优先级最高组的父组,再按UNION模式计算权限集。 |
||
PARENT_CRITICAL_EAGER |
查找优先级最高组的父组,再按EAGER模式计算权限集。 |
||
PARENT_CRITICAL_LAZY |
查找优先级最高组的父组,再按LAZY模式计算权限集。 |
||
PARENT_CRITICAL_INTERSECT |
查找优先级最高组的父组,再按INTERSECT模式计算权限集。 |
||
PARENT_OVERLOOK_UNION |
查找优先级最低组的父组,再按UNION模式计算权限集。 |
||
PARENT_OVERLOOK_EAGER |
查找优先级最低组的父组,再按EAGER模式计算权限集。 |
||
PARENT_OVERLOOK_LAZY |
查找优先级最低组的父组,再按LAZY模式计算权限集。 |
||
PARENT_OVERLOOK_INTERSECT |
查找优先级最低组的父组,再按INTERSECT模式计算权限集。 |
||
CHILD_HORIZON_UNION |
查找所有组的子组,再按UNION模式计算权限集。 |
||
CHILD_HORIZON_EAGER |
查找所有组的子组,再按EAGER模式计算权限集。 |
||
CHILD_HORIZON_LAZY |
查找所有组的子组,再按LAZY模式计算权限集。 |
||
CHILD_HORIZON_INTERSECT |
查找所有组的子组,再按INTERSECT模式计算权限集。 |
||
CHILD_CRITICAL_UNION |
查找优先级最高组的子组,再按UNION模式计算权限集。 |
||
CHILD_CRITICAL_EAGER |
查找优先级最高组的子组,再按EAGER模式计算权限集。 |
||
CHILD_CRITICAL_LAZY |
查找优先级最高组的子组,再按LAZY模式计算权限集。 |
||
CHILD_CRITICAL_INTERSECT |
查找优先级最高组的子组,再按INTERSECT模式计算权限集。 |
||
CHILD_OVERLOOK_UNION |
(无权限)查找优先级最低组的子组,再按UNION模式计算权限集。 |
||
CHILD_OVERLOOK_EAGER |
(无权限)查找优先级最低组的子组,再按EAGER模式计算权限集。 |
||
CHILD_OVERLOOK_LAZY |
(无权限)查找优先级最低组的子组,再按LAZY模式计算权限集。 |
||
CHILD_OVERLOOK_INTERSECT |
(无权限)查找优先级最低组的子组,再按INTERSECT模式计算权限集。 |
||
INHERIT_HORIZON_UNION |
查找所有组父组包含本组,再按UNION模式计算权限集。 |
||
INHERIT_HORIZON_EAGER |
查找所有组父组包含本组,再按EAGER模式计算权限集。 |
||
INHERIT_HORIZON_LAZY |
查找所有组父组包含本组,再按LAZY模式计算权限集。 |
||
INHERIT_HORIZON_INTERSECT |
查找所有组父组包含本组,再按INTERSECT模式计算权限集。 |
||
INHERIT_CRITICAL_UNION |
查找优先级高组的父组包含本组,再按UNION模式计算权限集。 |
||
INHERIT_CRITICAL_EAGER |
查找优先级高组的父组包含本组,再按EAGER模式计算权限集。 |
||
INHERIT_CRITICAL_LAZY |
查找优先级高组的父组包含本组,再按LAZY模式计算权限集。 |
||
INHERIT_CRITICAL_INTERSECT |
查找优先级高组的父组包含本组,再按INTERSECT模式计算权限集。 |
||
INHERIT_OVERLOOK_UNION |
查找优先级低组的父组包含本组,再按UNION模式计算权限集。 |
||
INHERIT_OVERLOOK_EAGER |
查找优先级低组的父组包含本组,再按EAGER模式计算权限集。 |
||
INHERIT_OVERLOOK_LAZY |
查找优先级低组的父组包含本组,再按LAZY模式计算权限集。 |
||
INHERIT_OVERLOOK_INTERSECT |
查找优先级低组的父组包含本组,再按INTERSECT模式计算权限集。 |
||
EXTEND_HORIZON_UNION |
查找所有组子组包含本组,再按UNION模式计算权限集。 |
||
EXTEND_HORIZON_EAGER |
查找所有组子组包含本组,再按EAGER模式计算权限集。 |
||
EXTEND_HORIZON_LAZY |
查找所有组子组包含本组,再按LAZY模式计算权限集。 |
||
EXTEND_HORIZON_INTERSECT |
查找所有组子组包含本组,再按INTERSECT模式计算权限集。 |
||
EXTEND_CRITICAL_UNION |
查找优先级高组的子组包含本组,再按UNION模式计算权限集。 |
||
EXTEND_CRITICAL_EAGER |
查找优先级高组的子组包含本组,再按EAGER模式计算权限集。 |
||
EXTEND_CRITICAL_LAZY |
查找优先级高组的子组包含本组,再按LAZY模式计算权限集。 |
||
EXTEND_CRITICAL_INTERSECT |
查找优先级高组的子组包含本组,再按INTERSECT模式计算权限集。 |
||
EXTEND_OVERLOOK_UNION |
查找优先级低组的子组包含本组,再按UNION模式计算权限集。 |
||
EXTEND_OVERLOOK_EAGER |
查找优先级低组的子组包含本组,再按EAGER模式计算权限集。 |
||
EXTEND_OVERLOOK_LAZY |
查找优先级低组的子组包含本组,再按LAZY模式计算权限集。 |
||
EXTEND_OVERLOOK_INTERSECT |
查找优先级低组的子组包含本组,再按INTERSECT模式计算权限集。 |
|
多态身份Profile是整个 Zero权限框架中的一个 过度设计 的典范,从实际场景看起来真正使用到这部分的内容仅局限于 对用户而言,一旦登录之后,自己的 Profile 就已经固定,而资源需求要求的Profile则不一定固定,属于变量,最终计算结果近似于查找最短路径,达到用户组这个级别的额外的变化模式(包括继承、包括派生、包括限制、包括组合等),最终 Zero权限框架中合计支持64种Profile配置,如此就解决了资源 能不能 访问的问题。 |
3.7.1.3.N维安全视图(View)
前文解决了资源 能不能 访问的问题,本章就在可访问的基础上解决 访问多少 的问题,Zero权限框架中的 S_RESOURCE 和 S_ACTION 是强绑定关系,它们之间只会单纯对比操作级别和资源需求级别是否可访问,一旦访问成功,就会衍生计算读写操作的边界,在Zero权限框架中读写边界的划定取决于安全视图 S_VIEW 中的定义。
安全视图基础
安全视图的基础维度如下:
| 字段名 | 含义 | 取值 |
|---|---|---|
NAME |
视图名称 |
默认取值 DEFAULT。 |
POSITION |
视图位置 |
默认取值 DEFAULT。 |
OWNER_TYPE |
视图所属者类型 |
只包含两种:ROLE-角色视图,USER-用户视图。 |
OWNER |
视图所属者ID |
如果是角色视图则是角色ID,如果是用户视图则是用户ID。 |
RESOURCE_ID |
视图所属资源 |
当前视图关联的 |
POSITION 和 NAME 构造的视图的核心维度,在系统出现不同需求时会起重要作用:
| 场景 | NAME | POSITION |
|---|---|---|
单模块无视图管理 |
DEFAULT |
DEFAULT |
单模块带视图 |
? |
DEFAULT |
多模块无视图管理 |
DEFAULT |
? |
多模块多视图管理 |
? |
? |
|
此处解释一下模块的概念,此处的 模块 并非我们开发过程中的模块,此处的模块底层关联模型只有一个,而模块更多是从列表作为入口。例如:
|
而且 POSITION 会比资源多一个维度,通常资源是后接口绑定,如 /api/xxx/search 的资源接口,但这个资源接口由于支持查询引擎语法,可能应用于不同的菜单入口(上述提到的正在执行的订单、已完成的订单)等,这种情况下两个菜单共享了一个资源,而为了针对不同的菜单定义 角色视图/用户视图,最好的方式就是启用 POSITION 参数。如此计算下来,POSITION既不和查询条件绑定(不同页面、不同位置、同一查询条件),也不可以和页面绑定,如果出现 TAB 页签会造成同一个页面中出现两种不同的查询(可能是两种不同的 POSITION),最终它只能和列表的配置绑定,直接在前端中提供它的配置来完成和列表绑定的过程,这一块的用法属于 Zero权限框架中的难点,其应用范围十分广泛,现阶段通常使用场景如下:
-
按类型划分位置信息:分类字段管理
/ambient/tabular/:type不同页面取不同的 POSITION,实现抽象态的列表管理,这种思路同样适用于:档案、合同、项目、员工、客户、分类等。 -
按类型划分位置信息:如待办列表和已办列表,最终访问资源可能都是
/api/todo/search,而由于状态不同,所以设置不同的 POSITION 实现视图的定制。 -
按流程划分QBE:目前系统中流程右上角的QBE列表页是基于此种逻辑,几乎不使用开发的模式就定制完成。
参考下图的结构:
上图结构可以看到 POSITION 和 NAME(在模块访问中通常使用 VIEW)的使用场景会有所差异:
-
POSITION主要用于模块维度的拓展,它的起点是模块。
-
VIEW主要用于视图拥有者维度的拓展,它的起点是拥有者,如角色、用户、目录级(实验版本)。
安全视图类
Zero中存在一个特殊的参数对象:
public class Vis extends JsonObject
// 该参数对象使用时可如下:
@POST
@Path("/{actor}/search")
@Address(Addr.Post.SEARCH)
@Adjust(Orders.MODULE)
JsonObject search(@PathParam("actor") String actor,
@BodyParam JsonObject data,
@QueryParam(KName.MODULE) String module,
@PointParam(KName.VIEW) Vis view);
该参数的格式比较特殊,通常使用的是 [view,position] 的数据格式,也是此处的 @PointParam 注解解析的内容,它可以将上述格式直接解析成视图的两个核心维度( NAME, POSITION ),并将该维度应用于任意支持它的接口。Vis类中存在一个特殊方法smart会对视图数据格式做智能解析,它支持的几种格式如下:
-
Vis类型:如果传入的类型是Vis类型,则直接做引用赋值。
-
JsonObject类型:如果传入的类型是JsonObject类型,则解析格式:
{"position": "xxx","view":"xxx"}。 -
JsonArray类型:如果传如的类型是JsonArray类型,则解析格式:`[view,position]`执行解析。
-
String类型:如果传如类型是String类型,除了完成URL的
decode流程之外:-
如果String类型是JsonArray格式则做一次强制转换,执行JsonObject类型解析。
-
如果String类型是JsonArray格式则做一次强制转换,执行JsonArray类型解析。
-
否则String类型参数直接作为视图名称看待,而赋
POSITION为默认值DEFAULT。
-
-
默认创建专用默认视图:
view = DEFAULT, position = DEFAULT。
窗口定义
安全视图的窗口定义主要依靠下边几个字段:
| 字段名 | 含义 |
|---|---|
PROJECTION |
JsonArray格式,执行该视图的列过滤,直接过滤掉接口返回数据的列信息。 |
CRITERIA |
JsonObject格式,执行该视图默认的 |
ROWS |
JsonObject格式,针对行数据执行筛选,生成 |
VISITANT |
布尔值,是否启用 虚拟视图(资源访问者)。 |
-
PROJECTION会作用于不同类型的前端组件,通常用于LIST/FORM两种,Zero框架中的保存列表的列信息以及表单中针对部分表单执行字段过滤就依赖它来完成,它是后置过滤(实际会从数据库中查询出所有信息进行值提取,现阶段没有明显的性能问题)。 -
CRITERIA主要针对于查询,它会隐式修改查询引擎的Qr语法,导致前端发送查询条件在安全视图作用下被直接修改,如果用户中出现了多个角色、多个用户组,则按照最终资源需求中定义的 Profile 来完成查询条件的拼合,默认模式下多个角色之间使用OR连接符。 -
ROWS针对特殊资源提取,提供基于主键的直接命中条件,解决异构查询模式下用户无法使用Join的情况,由于表单是单条数据记录,所以一般表单接口无法支持该属性(设置了也没有效果);通常此属性作用于列表:-
DATA:在数据层面,列表处理过程中直接针对条件执行过滤,典型应用为:菜单筛选、字典筛选、分类树筛选。
-
META:元数据层面,处理过程中只读取ROWS中设置过的的信息(特殊模式下载界面呈现模式出现ReadOnly时,它的定义位于UI配置中,而不是安全视图层。
-
|
虚拟视图(资源访问者)部分参考下一个章节的详细说明,上述限制中,虽然 |
视图检索流程
看完了上述安全视图的方方面面之后,视图检索流程就变得很简单了(后端会根据访问资源键值生成 session-<METHOD>:<URI>:<POSITION>/<VIEW> 格式的视图缓存键)。下边是用户访问某个资源接口时的详细流程:
-
用户发送请求到某个资源接口如:
/api/xxx/resource。 -
系统检索该资源是否存在用户级的
S_VIEW记录(OWNER_TYPE = USER, OWNER = <USER_ID>),如果存在该记录,则直接提取安全视图记录对资源执行前后(BEFORE/AFTER)计算。-
BEFORE计算:BEFORE计算的核心算法主要在于修改参数,它的性能更高,一般直接作用于 criteria 参数部分实现查询条件的注入流程。
-
AFTER计算:AFTER计算会完整访问数据库,在查询出来的结果集中做运算,虽然性能会有损耗,但在某些场景下(需要全量元数据)是必须要走AFTER的。
-
-
若不存在用户级的
S_VIEW记录,则继续检索是否有角色级的S_VIEW记录,若存在则计算。 -
上述两步都不存在时,忽略安全视图,可访问资源内所有内容。
|
从上述流程可以知道,用户级安全视图优先级比角色高,一般用户级安全视图都是个人视图模式存在,比如某个模块的视图管理,而角色级的视图都是管理员预设,单个用户不可以更改,比如管理员直接针对财务人员以外的角色设置不可访问某些资源的固定列如薪资、账期等。 |
最终 访问多少 的问题就直接被安全视图处理掉了,不同角色不同用户在此框架之下访问同一个接口时返回数据就可能出现不同,那么这样就解决了资源重用并且 访问多少 的问题。 :data-uri:
3.7.1.4.资源访问者(Visitor)
|
多用于抽象层次比较高的 动态建模 领域的权限控制。 |
本章进入Zero权限框架中的一个新的领域:虚拟资源/资源访问者,在讲虚拟资源之前先思考:为什么要使用虚拟资源?前文安全视图中的定义不知道读者是否发现一个小问题:静态的——一旦绑定了资源之后,就只能在某个资源中直接设置参数和条件,而这里设置的所有条件以及参数都 不依赖输入,您考虑下边一个场景。
我在后端书写了一个接口:/api/xxx/:type,这个接口在后端的定义的接口和 S_RESOURCE 记录仅有一条(为什么,除非您直接愿意写成 /api/xxx/type1 和 /api/xxx/type2 两个接口),这种场景下,意味着如果`type`有三个值,那么我所期望的安全视图应该有三种,根据前文提到的,您可以设置 S_VIEW 的 POSITION 来限定资源视图,就完成了三种模式下的定义。但若现在我的 :type 参数不是三种,可能存在N种或者上百种,如何解决?Zero为了解决这种安全视图方案,提供了 资源访问者 的概念。资源访问者存储于后端配置表 S_VISITANT 中,一个 S_VIEW(严格说是 VISITANT = true 的视图记录)可能包含无数个资源访问者。
Zero中按照如下流程配置一个资源访问者:
-
资源本身(
S_RESOURCE)将该资源定义成一个虚拟资源(VIRTUAL= true)。 -
在(
S_RESOURCE)资源字段中定义访问者基本规则:-
设置访问者语法
SEEK_SYNTAX字段。 -
设置访问者配置
SEEK_CONFIG字段。 -
设置访问者组件
SEEK_COMPONENT字段(Java类名)。
-
-
用户发送请求过来时会读取
S_VIEW的视图信息,一旦读取到视图信息后,对VISITANT= true执行校验,校验成功之后执行资源访问者流程。
资源访问者流程可以总结成两个大步骤:
| 步骤 | 说明 |
|---|---|
真实资源查找 |
根据 |
访问者安全视图 |
工具 |
访问者逻辑
资源访问者的内部逻辑流程如下:
-
检查访问视图
S_VIEW是否一个带有访问者的安全视图(VISITANT = true)的定义。 -
根据资源
S_RESOURCE中的访问者定义(seekSyntax / seekConfig / seekComponent)计算真实资源访问规则。 -
扫描资源访问者的 模式(Replace/Extend)和 作用阶段(EAGER/DELAY),并执行访问者操作。
-
读取访问者相关信息,将这些信息和
S_VIEW中的信息合并计算,计算访问者安全窗口。
(执行维度)模式和阶段
资源访问者语法中的模式 mode 信息:
-
Replace:替换模式,这种模式下,访问者视图会直接覆盖
S_VIEW中的安全窗口规则,也就是说资源访问不再遵循S_VIEW中的安全规则,而直接使用访问者规则。 -
Extend:扩展模式,这种模式下,访问者视图会和
S_VIEW中的安全窗口规则合并计算,形成新的组合好的访问者规则。
资源访问者语法中的阶段 phase 信息:
-
EAGER:通常当前资源立即生效,一般执行数据读取时会使用 EAGER 阶段(就在当前接口生效)。
-
DELAY:这种阶段通常是读取配置项作用于子资源或其他资源时生效,一般读取元数据和配置数据时使用 DELAY 阶段(DELAY阶段 DataRegion 中的视图模式依旧生效)。
(定义维度)类型/标识/唯一键
执行资源访问者需要具备两个条件:S_RESOURCE 中的执行维度、S_VIEW 中的定义维度,而定义维度就是依赖参数去查找当前 S_VIEW 中所拥有的众多资源访问者中的其中